<?php

namespace simpleHandle\Component\UtilTool;

use simpleHandle\Exception\UtilException;
use Throwable;
use voku\helper\AntiXSS;

class Tool
{

    /**
     * 生产随机数
     *
     * @param int $length 长度
     * @param string $alphabet 元素集合
     * @return false|string
     * @throws UtilException
     */
    public static function character(int $length = 6, string $alphabet = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'): string
    {
        try {
            mt_srand();
            if ($length >= strlen($alphabet)) {
                $rate     = (int)($length / strlen($alphabet)) + 1;
                $alphabet = str_repeat($alphabet, $rate);
            }

            return substr(str_shuffle($alphabet), 0, $length);
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 生产一个UUID4
     * 有概率重复|短时间内可以认为唯一
     *
     * @return string
     * @throws UtilException
     */
    public static function makeUUIDV4(): string
    {
        try {
            mt_srand();
            $charId = strtolower(md5(uniqid(mt_rand(), true)));
            $hyphen = '-';

            return substr($charId, 0, 8) . $hyphen .
                   substr($charId, 8, 4) . $hyphen .
                   substr($charId, 12, 4) . $hyphen .
                   substr($charId, 16, 4) . $hyphen .
                   substr($charId, 20, 12);
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 雪花唯一ID
     *
     * @param string $prefix
     * @param int $dataCenterId
     * @param int $workerId
     * @return string
     * @throws UtilException
     */
    public static function makeSnowFlake(string $prefix = "", int $dataCenterId = 0, int $workerId = 0): string
    {
        try {
            $sf = SnowflakeInstance::getInstance($dataCenterId, $workerId);
            $sf = $sf->setStartTimeStamp(time());
            $id = $sf->id();
            return $prefix . $id;
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 生成订单号 不设置前缀为22位订单号
     *
     * @param string $prefix
     * @param int $dataCenterId
     * @param int $workerId
     * @return string
     * @throws UtilException
     */
    public static function makeOrderSn(string $prefix = "", int $dataCenterId = 0, int $workerId = 0): string
    {
        try {
            $sf = SnowflakeInstance::getInstance($dataCenterId, $workerId);
            $sf->setStartTimeStamp(time());
            $str = $sf->id();
            return $prefix . date('ymdHis') . substr($str, 12, 7) . self::character(3, '0123456789');
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 获取当前微秒
     *
     * @throws UtilException
     */
    public static function getMicroTime(): string
    {
        try {
            [$usc, $sec] = explode(" ", microtime());

            return ($sec . substr($usc, 2, 3));
        } catch (\Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * XSS过滤
     *
     * @param string $value
     * @param false $isClean
     * @return bool|mixed|string|string[]|null
     */
    public static function checkXss(string $value, bool $isClean = false)
    {
        $antiXss         = new AntiXSS();
        $harmless_string = $antiXss->xss_clean($value);
        if ($isClean) {
            return $harmless_string;
        }

        return $antiXss->isXssFound();
    }

    /**
     * 获取当日时间
     *
     * @throws UtilException
     */
    public static function getTodayZero()
    {
        try {
            return strtotime(date('Y-m-d 00:00:00', time()));
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 获取本周所有日期
     *
     * @param int $time 时间戳
     * @param string $format 格式
     * @return array
     * @throws UtilException
     */
    public static function weekList(int $time = 0, string $format = 'Y-m-d'): array
    {
        try {
            $time = $time > 0 ? $time : time();
            $week = date('w', $time);
            $date = [];
            for ($i = 1; $i <= 7; $i++) {
                $date[$i] = date($format, strtotime('+' . ($i - $week) . ' days', $time));
            }

            return $date;
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 时间显示函数
     *
     * @param int or string $unixTime 时间戳或者时间字符串
     * @param int $limit 相差时间间隔
     * @param string $format 超出时间间隔的日期显示格式
     * @return string 返回需要的时间格式
     * @throws UtilException
     */
    public static function showTime(int $unixTime, int $limit = 18000, string $format = "Y-m-d"): string
    {
        try {
            $nowTime = time();
            if (!is_int($unixTime)) {
                $unixTime = strtotime($unixTime);
            }
            $differ = $nowTime - $unixTime;
            if ($differ >= 0) {
                if ($differ > $limit) {
                    $showtime = date($format, $unixTime);
                } else {
                    $showtime = $differ > 86400 ? floor($differ / 86400) . "天前" : ($differ > 3600 ? floor($differ / 3600) . "小时前" : floor($differ / 60) . "分钟前");
                }
            } else {
                if (-$differ > $limit) {
                    $showtime = date($format, $unixTime);
                } else {
                    $showtime = -$differ > 86400 ? floor(-$differ / 86400) . "天" : (-$differ > 3600 ? floor(-$differ / 3600) . "小时" : floor(-$differ / 60) . "分钟");
                }
            }

            return $showtime;
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 获取当前时间和参数时间相差的天数
     *
     * @param int $timestamp
     * @return float|int
     * @throws UtilException
     */
    public static function diffDayNum(int $timestamp)
    {
        try {
            $nowDay = date("Y-m-d");
            $sysDay = date("Y-m-d", $timestamp);
            $day    = strtotime($nowDay) - strtotime($sysDay);

            return $day / 86400;
        } catch (Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 时间差计算
     *
     * @param int $timestamp
     * @return string
     * @throws UtilException
     */
    public static function roundTime(int $timestamp): string
    {
        try {
            $now  = time();
            $time = $timestamp - $now;
            if ($time > 0) {
                $suffix = '之后';
            } else {
                $suffix = '之前';
            }
            $fixTime = $roundTime = "";
            $time    = abs($time);
            if ($time < 60) {
                $fixTime   = '秒';
                $roundTime = $time;
            } elseif ($time < 3600) {
                $fixTime   = '分钟';
                $roundTime = round($time / 60);
            } elseif ($time < 3600 * 24) {
                $fixTime   = '小时';
                $roundTime = round($time / 3600);
            } elseif ($time < 3600 * 24 * 7) {
                $fixTime   = '天';
                $roundTime = round($time / (3600 * 24));
            } elseif ($time < 3600 * 24 * 30) {
                $fixTime   = '周';
                $roundTime = round($time / (3600 * 24 * 7));
            } elseif ($time < 3600 * 24 * 365) {
                $fixTime   = '个月';
                $roundTime = round($time / (3600 * 24 * 30));
            } elseif ($time >= 3600 * 24 * 365) {
                $fixTime   = '年';
                $roundTime = round($time / (3600 * 24 * 365));
            }

            return $roundTime . ' ' . $fixTime . $suffix;
        } catch (\Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 获取某天的开始结束时间
     *
     * @param string $date
     * @return array
     * @throws UtilException
     */
    public static function startAndEndTimestamp(string $date = ''): array
    {
        try {
            $date      = empty($date) ? date('Y-m-d') : $date;
            $startTime = strtotime($date);
            $endTime   = $startTime + 60 * 60 * 24;

            return [$startTime, $endTime];
        } catch (\Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * @description 获取当前日期的【一周，一个月】日期列表
     * @param string $type 类型
     * @param int $time 时间
     * @param string $format 格式
     * @return array
     * @throws UtilException
     */
    public static function getDateList(string $type = "W", int $time = 0, string $format = 'Y-m-d'): array
    {
        try {
            $time = $time > 0 ? $time : time();
            //获取当前周几
            $number  = $type === "W" ? date('w', $time) : date('d', $time);
            $date    = [];
            $allDays = $type === "W" ? 7 : date("t", $time);
            for ($i = 1; $i <= $allDays; $i++) {
                $date[] = date($format, strtotime('+' . ($i - $number) . ' days', $time));
            }

            return $date;
        } catch (\Throwable $th) {
            throw new UtilException($th->getMessage(), UtilException::UtilTool_ERROR_CODE);
        }
    }

    /**
     * 创建目录
     *
     * @param string $dirPath 需要创建的目录
     * @param integer $permissions 目录权限
     * @return bool
     */
    public static function createDirectory(string $dirPath, int $permissions = 0755): bool
    {
        if (!is_dir($dirPath)) {
            try {
                $bool = mkdir($dirPath, $permissions, true);
                return $bool && chmod($dirPath, $permissions);
            } catch (\Throwable $throwable) {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * 清空一个目录
     *
     * @param string $dirPath 需要创建的目录
     * @param bool $keepStructure 是否保持目录结构
     * @return bool
     */
    public static function cleanDirectory(string $dirPath, bool $keepStructure = false): bool
    {
        $scanResult = static::scanDirectory($dirPath);
        if (!$scanResult) {
            return false;
        }
        try {
            foreach ($scanResult['files'] as $file) {
                unlink($file);
            }
            if (!$keepStructure) {
                krsort($scanResult['dirs']);
                foreach ($scanResult['dirs'] as $dir) {
                    rmdir($dir);
                }
            }

            return true;
        } catch (\Throwable $throwable) {
            return false;
        }
    }

    /**
     * 删除一个目录
     *
     * @param string $dirPath
     * @return bool
     */
    public static function deleteDirectory(string $dirPath): bool
    {
        $dirPath = realpath($dirPath);
        if (!is_dir($dirPath)) {
            return false;
        }
        if (!static::cleanDirectory($dirPath)) {
            return false;
        }

        return rmdir(realpath($dirPath));
    }

    /**
     * 复制目录
     *
     * @param string $source 源位置
     * @param string $target 目标位置
     * @param bool $overwrite 是否覆盖目标文件
     * @return bool
     */
    public static function copyDirectory(string $source, string $target, bool $overwrite = true): bool
    {
        $scanResult = static::scanDirectory($source);
        if (!$scanResult) {
            return false;
        }
        if (!is_dir($target)) {
            self::createDirectory($target);
        }
        try {
            $sourceRealPath = realpath($source);
            foreach ($scanResult['files'] as $file) {
                $targetRealPath = realpath($target) . '/' . ltrim(substr($file, strlen($sourceRealPath)), '/');
                static::copyFile($file, $targetRealPath, $overwrite);
            }

            return true;
        } catch (\Throwable $throwable) {
            return false;
        }
    }

    /**
     * 移动目录到另一位置
     *
     * @param string $source 源位置
     * @param string $target 目标位置
     * @param bool $overwrite 是否覆盖目标文件
     * @return bool
     */
    public static function moveDirectory(string $source, string $target, bool $overwrite = true): bool
    {
        $scanResult = static::scanDirectory($source);
        if (!$scanResult) {
            return false;
        }
        if (!is_dir($target)) {
            self::createDirectory($target);
        }
        try {
            $sourceRealPath = realpath($source);
            foreach ($scanResult['files'] as $file) {
                $targetRealPath = realpath($target) . '/' . ltrim(substr($file, strlen($sourceRealPath)), '/');
                static::moveFile($file, $targetRealPath, $overwrite);
            }
            static::deleteDirectory($sourceRealPath);

            return true;
        } catch (\Throwable $throwable) {
            return false;
        }
    }

    /**
     * 复制文件
     *
     * @param string $source 源位置
     * @param string $target 目标位置
     * @param bool $overwrite 是否覆盖目标文件
     * @return bool
     */
    public static function copyFile(string $source, string $target, bool $overwrite = true): bool
    {
        if (!file_exists($source)) {
            return false;
        }
        if ($overwrite === false && file_exists($target)) {
            return false;
        }

        if ($overwrite === true && file_exists($target) && !unlink($target)) {
            return false;
        }
        $targetDir = dirname($target);
        if (!self::createDirectory($targetDir)) {
            return false;
        }

        return copy($source, $target);
    }

    /**
     * 创建一个空文件
     *
     * @param string $filePath
     * @param bool $overwrite
     * @return bool
     */
    public static function touchFile(string $filePath, bool $overwrite = true): bool
    {
        if ($overwrite === false && file_exists($filePath)) {
            return false;
        }

        if ($overwrite === true && file_exists($filePath)) {
            if (!unlink($filePath)) {
                return false;
            }
        }
        $aimDir = dirname($filePath);
        if (self::createDirectory($aimDir)) {
            try {
                return touch($filePath);
            } catch (\Throwable $throwable) {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 创建一个有内容的文件
     *
     * @param string $filePath
     * @param string $content
     * @param bool $overwrite
     * @return bool
     */
    public static function createFile(string $filePath, string $content, bool $overwrite = true): bool
    {
        if (static::touchFile($filePath, $overwrite)) {
            return (bool)file_put_contents($filePath, $content);
        }

        return false;
    }

    /**
     * 移动文件到另一位置
     *
     * @param string $source 源位置
     * @param string $target 目标位置
     * @param bool $overwrite 是否覆盖目标文件
     * @return bool
     */
    public static function moveFile(string $source, string $target, bool $overwrite = true): bool
    {
        if (!file_exists($source)) {
            return false;
        }
        if ($overwrite === false && file_exists($target)) {
            return false;
        }

        if ($overwrite === true && file_exists($target) && !unlink($target)) {
            return false;
        }
        $targetDir = dirname($target);
        if (!self::createDirectory($targetDir)) {
            return false;
        }

        return rename($source, $target);
    }

    /**
     * 遍历目录
     *
     * @param string $dirPath
     * @return array|bool
     */
    public static function scanDirectory(string $dirPath)
    {
        if (!is_dir($dirPath)) {
            return false;
        }
        $dirPath       = rtrim($dirPath, '/') . '/';
        $dirs          = array($dirPath);
        $fileContainer = array();
        $dirContainer  = array();
        try {
            do {
                $workDir    = array_pop($dirs);
                $scanResult = scandir($workDir);
                foreach ($scanResult as $files) {
                    if ($files === '.' || $files === '..') {
                        continue;
                    }
                    $realPath = $workDir . $files;
                    if (is_dir($realPath)) {
                        $dirs[]         = $realPath . '/';
                        $dirContainer[] = $realPath;
                    } elseif (is_file($realPath)) {
                        $fileContainer[] = $realPath;
                    }
                }
            } while ($dirs);
        } catch (\Throwable $throwable) {
            return false;
        }

        return ['files' => $fileContainer, 'dirs' => $dirContainer];
    }
}